{***************************************************************
    SpyGestion  Copyright (C) 2008 Christophe KOALI <nature2gas@gmail.com>
    
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
    
****************************************************************}
unit modSondesAttaque;

interface

uses Classes, SysUtils,
     modBaseType,
     Math,
     ESBDates,
     modRapport,
     modCore,
     modSondageList,
     modBaseDeDonnesRapport,
     sFonctionsChaines;

const
    INDICE_FIRST_ATTACK = 0;
    INDICE_LAST_ATTACK  = 2;

    MAX_SLOT            = 19;

type



    TAttSondeCible = record
        // Coordonnées
        Coord: TPlanetLocation;

        // Nombre d'attaque déjà effectué
        NbAttaqueDejaFaite: integer;

        // Négatif tant que la dernière attaque n'a pas été effectué
        DateDeDerniereAttaque: int64;
        DateDePremiereAttaque: int64;
        IdVagueDePremiereAttaque : integer;


        // Nombre d'erreur concernant cette planète
        NbErreur: integer;

        // Pointeur vers le rapport
        Rapport: PRapportHead;

        // Nombre de ressource initiale
        RessourcesInitiales: integer;

        // Validité des ressources
        RessourcesValidide: int64;

        // Ressource déjà prise
        RessourcesDejaPrise: integer;


        // Définit si il a été pushé en tête de liste (la date de push)
        Pushed: int64;

        // Définit le nombre de sondes pouvant être envoyé en prenant
        // compte de la production des mines (donc du temps, ce qui
        // implique que ce champ est mit à jour régulièrement)
        NbSondesPossible: array[INDICE_FIRST_ATTACK..INDICE_LAST_ATTACK] of integer;
        NbSondesPossibleForCurr: integer;

        // Pour la recherche en cours, définit si cet indice est à ignorer
        TooMuch: boolean;
    end;

    PAttSondeCible = ^TAttSondeCible;


    TAttScanIA = record
        Link: PAttSondeCible;
        NbSondesPossibleForCurr: integer;
        IdVagueDePremiereAttaque : integer;
    end;



    // Cela est obligatoire car il se peut que l'on attaque deux fois
    // d'affilé la même planète, et alors le champ NbAttaqueDejaFaite
    // ne permet plus de distinguer quelle attaque on effectue
    TAttSlot = record
       NbSondes: integer;
       Link: PAttSondeCible;
    end;
    PAttSlot = ^TAttSlot;

    TCollAttSlot = class(TList)
    private

       {$IFDEF DEBUG}
          // information de débogage
          FDebugProc: TsfcProcStatus;
       {$ENDIF}

       {$IFDEF DEBUG}
           procedure DoDebug(const Msg: string);
       {$ENDIF}
    public
       CurrIdVague: integer;

       {$IFDEF DEBUG}
           procedure AfficheDebug;
           property DebugProc: TsfcProcStatus read FDebugProc write FDebugProc;
       {$ENDIF}


        procedure Delete(Index: integer);
        function Push(NbSondes: integer; Lien: PAttSondeCible): PAttSlot;
        procedure Clear; override;
    end;

    TCoupleAtt = array[0..MAX_SLOT] of PAttSondeCible;


    TCollectionAttSondeCible = class(TList)
    private
       {--------------------------------------------------}
       {Elements spécialisés de la classes}
       FCurrDate: int64;

       // Nombre de pusher restant à traiter
       FNbPushedRestant: integer;

       // Nombre de sondes totales, c'est à dire le nombre de sondes
       // qui sont envoyées lors d'une première attaque sur un joueur plein de ressource
       // C'est le nombre de sondes totales disponible
       FSondesTotales: integer;


       // Correspond au nombre de slot maximum dédié aux sondes
       // CEPENDANT, si on peut envoyer toutes les sondes sur une cible
       // on occupera qu'un seul slot
       FSlotMaxToUse: integer;

       
       // Planete à partir de laquelle on attaque
       FPlaneteSystemSolaireAtt: integer;


       // Nombre de sonde à quai pour ne pas continuer la recherche d'occupation de slot
       FSondesAQuaiTolere: integer;

       // Nombre de sondes minimal à envoyer sur une cible pour ignorer l'attaque
       FSondesMinToAttack: integer;

       // Nombre de sondes minimales à envoyer dans une vague de N attaques
       FSondesMinParVague: integer;


       // Attaque à effectuer pour une "vague" (pas forcément sur la même cible)
       FListVague: TCollAttSlot;


       // Nombre de vagues effectuées (une vague correspond à 2 fois 2 mins, c'est
       // à dire 4 mins, on peut alors transformer la contrainte de temps
       // en une contrainte de nombre de vague effectuée)
       FNbVagueEffectue: integer;


       FSearchBestCouple     : TCoupleAtt;
       FSearchBestCoupleScore: integer;
       FSearchBestCoupleLimit: integer;
       FSearchMaxIndice      : integer;
       FSearchMinIndice      : integer;

       FCible1: PAttSondeCible;
       FCible2: PAttSondeCible;

       {$IFDEF DEBUG}
          // information de débogage
          FDebugProc: TsfcProcStatus;
       {$ENDIF}
        
       {--------------------------------------------------}
       {Gestion des pointeurs}
       // Utilisé pour la propriété Itemx
       function GetItemx(Index: integer): TAttSondeCible;

       procedure RefreshnbSondes(pe: PAttSondeCible);

       {$IFDEF DEBUG}
           procedure DoDebug(const Msg: string);
           procedure SetDebugProc(Valeur: TsfcProcStatus);
       {$ENDIF}

       procedure ReSetTooMuch(MaxSondes: integer);

       procedure IncAtt(pe: PAttSondeCible);
    public
       {$IFDEF DEBUG}
           procedure AfficheDebug;
           property DebugProc: TsfcProcStatus read FDebugProc write SetDebugProc;
       {$ENDIF}

       property SondesTotales : integer read FSondesTotales write FSondesTotales;
       property PlaneteSystemSolaireAtt: integer read FPlaneteSystemSolaireAtt write FPlaneteSystemSolaireAtt;
       property SlotMaxToUse: integer read FSlotMaxToUse write FSlotMaxToUse;

       property SondesMinParVague: integer read FSondesMinParVague write FSondesMinParVague;
       property SondesMinToAttack: integer read FSondesMinToAttack write FSondesMinToAttack;




       property Vague: TCollAttSlot read FListVague;
       property LastIdVague: integer read FNbVagueEffectue;

       {--------------------------------------------------}
       {Gestion des pointeurs}
       // *** Ajout de pointeurs
       // Renvoi le pointeur de l'élément ajouté
       function Add(Element: TAttSondeCible): PAttSondeCible;
      
       // Simple ajout de pointeur
       procedure SimpleAdd(Pointeur: PAttSondeCible);
      
       // Renvoi le pointeur de l'élément ajouté/modifié
       // Utilise Get_Index pour vérifier si l'élément doit être mis à jour
       function Add_Update(Element: TAttSondeCible): PAttSondeCible;
      
      
       // *** Suppression de pointeur
       // Libère et supprime le pointeur Index
       // Renvoi True si réussit, False si hors limite ou erreur
       function Delete(Index: integer): boolean; 

      
       // *** Effacement de la liste de pointeur
       procedure Clear; override;
      
      
       // *** Requete sur la liste de pointeur
       function Get_Index(Element: TPlanetLocation): integer;
       function Get_Pointeur(Element: TPlanetLocation): PAttSondeCible;
       function Get_Item(Index: integer): TAttSondeCible;
       property Itemx[Index: integer]: TAttSondeCible read GetItemx;



       function Import(List: TCollectionPlaneteASonder): integer;
       function BuildStats(DB: TCollectionRapportHead): integer;
       procedure RefreshNbSondeToSend;


       function AddIfNotExistCoord(Element: TPlanetLocation): PAttSondeCible;

       function BuildNextVagueToAttack: integer;
       function BuildNextVagueToAttack_Original: integer;


       {--------------------------------------------------}
       {Procédures et fonctions spécialisées de la classes}
       // Destructeur
       destructor Destroy; override;
       constructor Create;

       function EvalCouple(c: TCoupleAtt): integer;

       procedure Trie;
       function StartSearch(BaseCouple: TCoupleAtt; Score: integer; nSlot: integer): integer;
    end;

    
(************************************************************************
 Problème:
 Soit une série de k nombre n_1, n_2, n_3, ... n_k
 Soit un entier s >= 1
 En choisissant au plus s nombres, lesquels doit on prendre
 pour être le plus proche (par défaut) d'un nombre N ?


 Si à chacun des nombres on associe un entier p compris entre 0 et 7
 Refaire l'algorithme de telle sorte



*************************************************************************)
implementation

var
   TrieCritereProximite: integer;

procedure InitCouple(var c: TCoupleAtt);
var i: integer;
begin

   for i := 0 to MAX_SLOT do
      c[i] := nil;

end;

procedure TCollAttSlot.Clear;
var i: integer;
    pElement: PAttSlot;
begin

    for i := Count - 1 downto 0 do begin
       Delete(i);
    end;

end;

procedure TCollAttSlot.Delete(Index: integer);
var pElement: PAttSlot;
begin

    if (Index < 0) or (Index >= Count) then
       // Hors limite
       exit;

    // Obtient le pointeur
    pElement := Items[Index];

    if pElement <> nil then
       // Libére la mémoire créée lors de l'ajout
       Dispose(pElement);

    // Supprime le pointeur de la liste
    inherited Delete(Index);

end;

{$IFDEF DEBUG}
   procedure TCollAttSlot.DoDebug(const Msg: string);
   begin
       if Assigned(FDebugProc) then
          FDebugProc('TCollAttSlot - ' + Msg);

   end;

   procedure TCollAttSlot.AfficheDebug;
   var i, diff: integer;
       pe: PAttSlot;
       str, sAdd: string;
   begin
       if Count = 0 then begin
          DoDebug('Aucun élément dans la vague');
          exit;
       end;



       for i := 0 to Count - 1 do begin
           pe := Items[i];

           diff := CurrIdVague - pe.Link.IdVagueDePremiereAttaque;

           if pe.Link.NbAttaqueDejaFaite = 1 then begin
              sAdd := format('Premiere attaque', [pe.Link.IdVagueDePremiereAttaque]);

           end else if pe.Link.NbAttaqueDejaFaite = 2 then begin
              sAdd := format('Deuxième attaque - Difference: %d', [diff]);

           end else if  pe.Link.NbAttaqueDejaFaite = 3 then begin
              sAdd := format('Dernière attaque - Difference: %d   - Id Vague start: %d', [diff, pe.Link.IdVagueDePremiereAttaque]);

           end else begin
              sAdd := 'ERREUR';
           end;


           str := format('Slot %d:  %s  ->  %d sondes %s', [i + 1, mbtPlanetLocationToStr(pe.Link.Coord), pe.NbSondes, sAdd]);
           DoDebug(str);
       end;

   end;

{$ENDIF}




function TCollAttSlot.Push(NbSondes: integer; Lien: PAttSondeCible): PAttSlot;
var pElement: PAttSlot;
begin
    
    Result := nil;

    New(pElement);          // Créer l'espace mémoire requis
    pElement.NbSondes := NbSondes;
    pElement.Link     := Lien;

    try
       inherited Add(pElement);  // Ajoute le pointeur à la liste
       Result := pElement;
    except
       Result := nil;
    end;

end;

{*******************************************************************************
 ******************************************************************************* 
 **               TCollectionAttSondeCible                                     **
 *******************************************************************************
 *******************************************************************************}

function TCollectionAttSondeCible.StartSearch(BaseCouple: TCoupleAtt; Score: integer; nSlot: integer): integer;
var i: integer;
    r: integer;
    pe: PAttSondeCible;
begin

    Result := -1;

    for i := FSearchMinIndice to FSearchMaxIndice do begin
       pe := Items[i];

       if pe.TooMuch then Continue;

       BaseCouple[nSlot - 2] := pe;

       r := Score - pe.NbSondesPossibleForCurr;

       if (r < 0) then begin
          // trop de sonde pour cet élément

       end else if r <= FSondesAQuaiTolere then begin
          // parfait
          Result := 2;
          FSearchBestCoupleLimit := 0;
          exit;

       end else begin
          if r < FSearchBestCoupleLimit then begin
             FSearchBestCoupleLimit := r;
             FSearchBestCouple      := BaseCouple;
          end;

          if nSlot < FSlotMaxToUse then begin
             // essaye de voir si on peut utiliser l'autre slot
             pe.TooMuch := true;
             if (StartSearch(BaseCouple, r, nSlot + 1) > 0) then begin
                Result := 1;
                exit;
             end;

             pe.TooMuch := false;
          end;

       end;

    end;


end;


function TCollectionAttSondeCible.EvalCouple(c: TCoupleAtt): integer;
var pe: PAttSondeCible;
    i, k, j: integer;
begin

     Result := 0;

     for i := 0 to MAX_SLOT do begin
         pe := c[i];

         if pe <> nil then begin
            Result := Result  + pe.NbSondesPossible[pe.NbAttaqueDejaFaite];

            (*
            for k := i + 1 to MAX_SLOT do begin
               if c[i] = c[k] then begin
                  // deux indices identique
                  Result := -1;
                  exit;
               end else if c[k] = nil then
                  break;

            end;
            *)

         end else
            break;

     end;

end;


procedure TCollectionAttSondeCible.ReSetTooMuch(MaxSondes: integer);
var i: integer;
    pe: PAttSondeCible;
begin

    for i := 0 to Count - 1 do begin
       pe := Items[i];


       if pe.NbSondesPossible[pe.NbAttaqueDejaFaite] > MaxSondes then begin
          // trop de sonde
          pe.TooMuch := true
       end;

    end;

end;


//////////////////////////////////////////////////////////////////////////
// Méthode originale (1 par 1)
// mais légèrement améliorée
// L'amélioration consiste à envoyer deux vagues sur une même cible si possible
function TCollectionAttSondeCible.BuildNextVagueToAttack_Original: integer;
var i: integer;
    pe: PAttSondeCible;
    temporelDiff: integer;
    bTrouve: boolean;
begin

    FListVague.Clear;

    Result := 0;
    if Count = 0 then exit;

    Inc(FNbVagueEffectue);
    FListVague.CurrIdVague := FNbVagueEffectue;

    bTrouve := false;

    for i := 0 to Count - 1 do begin
       pe := Items[i];

       if pe.DateDeDerniereAttaque > 0 then begin
          // déjà attaqué
          pe.TooMuch := true;
          Continue;
       end;

       pe.TooMuch := false;
       pe.NbSondesPossibleForCurr := pe.NbSondesPossible[pe.NbAttaqueDejaFaite];


       if pe.IdVagueDePremiereAttaque > 0 then begin
          // déjà attaqué
          // on finit les attaques sur lui
          temporelDiff := FNbVagueEffectue - pe.IdVagueDePremiereAttaque;

          if temporelDiff > 5 then begin
             // trop tard pour l'attaquer

             {$IFDEF DEBUG}
               DoDebug('Trop tard pour attaquer: ' + mbtPlanetLocationToStr(pe.Coord));
             {$ENDIF}

             pe.DateDeDerniereAttaque := DateTimeToUnix(Now);
             pe.TooMuch := true;
             Continue;
          end;

       end;

       // Sinon c'est une nouvelle cible
       if pe.NbSondesPossibleForCurr < FSondesMinParVague then begin
          // Note: on utilise FSondesMinParVague car l'ancienne méthode utilise
          // qu'un seul slot, donc une vague = une seul attaque
          {$IFDEF DEBUG}
             DoDebug('Plus assez de sondes: ' + mbtPlanetLocationToStr(pe.Coord));
          {$ENDIF}

          pe.TooMuch := true;
          pe.DateDeDerniereAttaque := DateTimeToUnix(Now);
          Continue;
       end;

       // sinon, on l'attaque
       bTrouve := true;
       break;

    end;

    
    if bTrouve then begin
       // ajoute à la vague
       FListVague.Push(pe.NbSondesPossibleForCurr, pe);

       IncAtt(pe);
       
       Result := 1;

    end else begin
       Result := 0;
    end;

end;


(***********************************************************
 La cible principale correspond soit:
 - A l'élément sur lequel il faut envoyer le plus de sondes SI plusieurs
   élément ont déjà été attaqué
 - Au premier élément non attaqué de la liste SI aucun élément n'a été attaqué


 La cible secondaire correspond soit:
 - A un élément déjà attaqué qui utilise au mieu le nombre de sonde restante
 - A l'élément qui utilise au mieux le nombre de sondes restante

 Element déjà attaqué mais en attente d'autre attaque maximun: 3

 

***********************************************************)


procedure TCollectionAttSondeCible.IncAtt(pe: PAttSondeCible);
begin

    Inc(pe.NbAttaqueDejaFaite);
    if pe.NbAttaqueDejaFaite = 3 then begin
       pe.DateDeDerniereAttaque   := DateTimeToUnix(Now);
       pe.NbSondesPossibleForCurr := 0;
       pe.TooMuch := true;
    end else if pe.NbAttaqueDejaFaite < 3 then begin
       pe.NbSondesPossibleForCurr := pe.NbSondesPossible[pe.NbAttaqueDejaFaite];

       if pe.NbAttaqueDejaFaite = 1 then begin
          pe.DateDePremiereAttaque    := DateTimeToUnix(Now);
          pe.IdVagueDePremiereAttaque := FNbVagueEffectue;
       end;
    end;

end;


function TCollectionAttSondeCible.BuildNextVagueToAttack: integer;
label lblFin;
var i, vDiff: integer;
    pe, pecmp: PAttSondeCible;
    NbSondesRestante: integer;
    c: TCoupleAtt;

    IndexPremierElementToAttack: integer;
    IndexSecondElementToAttack : integer;

    IndexBestElementDejaAttack: integer;
    ScoreBestElementDejaAttack: integer;
    NbDejaAttackEnAttente     : integer;
begin

    FListVague.Clear;

    Result := 0;
    if Count = 0 then exit;

    Inc(FNbVagueEffectue);
    FListVague.CurrIdVague := FNbVagueEffectue;

    IndexPremierElementToAttack := -1;
    ScoreBestElementDejaAttack  := -1;
    IndexBestElementDejaAttack  := -1;
    NbDejaAttackEnAttente       :=  0;
    IndexSecondElementToAttack  := -1;

    for i := 0 to Count - 1 do begin
       pe := Items[i];

       if pe.DateDeDerniereAttaque > 0 then begin
          // déjà attaqué
          pe.TooMuch := true;
          Continue;
       end;

       pe.TooMuch := false;
       pe.NbSondesPossibleForCurr := pe.NbSondesPossible[pe.NbAttaqueDejaFaite];


       if pe.IdVagueDePremiereAttaque > 0 then begin
          vDiff := FNbVagueEffectue - pe.IdVagueDePremiereAttaque;

          if vDiff > 5 then begin
             // trop tard pour l'attaquer
             {$IFDEF DEBUG}
                DoDebug('Trop tard pour attaquer: ' + mbtPlanetLocationToStr(pe.Coord) + ' Attaque deja faite: ' + IntToStr(pe.NbAttaqueDejaFaite) + ' - Sondes possible: ' + IntToStr(pe.NbSondesPossibleForCurr));
             {$ENDIF}


             pe.DateDeDerniereAttaque := DateTimeToUnix(Now);
             pe.TooMuch := true;
             Continue;
          end else if vDiff = 5 then begin
             // on doit l'attaquer, maintenant
             if pe.NbSondesPossibleForCurr < FSondesMinToAttack then begin
                {$IFDEF DEBUG}
                   DoDebug('Plus assez de sondes: ' + mbtPlanetLocationToStr(pe.Coord));
                {$ENDIF}
                
                pe.TooMuch := true;
                pe.DateDeDerniereAttaque := DateTimeToUnix(Now);
                Continue;
             end;
          end;

       end;



       if (IndexSecondElementToAttack < 0) and (IndexPremierElementToAttack >= 0) then
          IndexSecondElementToAttack  := i;

       if IndexPremierElementToAttack < 0 then
          IndexPremierElementToAttack := i;



       if pe.IdVagueDePremiereAttaque > 0 then begin
          if pe.NbSondesPossibleForCurr > ScoreBestElementDejaAttack then begin
             ScoreBestElementDejaAttack := pe.NbSondesPossibleForCurr;
             IndexBestElementDejaAttack := i;
          end;

          Inc(NbDejaAttackEnAttente);
       end;

    end;


    if IndexBestElementDejaAttack >= 0 then begin
       // c'est lui que l'on doit attaquer
       pe := Items[IndexBestElementDejaAttack];

       if NbDejaAttackEnAttente < 2 then begin

       end;

    end else begin
       if IndexPremierElementToAttack < 0 then begin
          // plus d'élément à attaquer
          Result := -1;
          exit;
       end;

       pe := Items[IndexPremierElementToAttack];
    end;

    if IndexSecondElementToAttack >= 0 then begin
       pecmp := Items[IndexSecondElementToAttack];

       if pecmp.NbSondesPossibleForCurr > pe.NbSondesPossibleForCurr then begin
          pe := pecmp;
          IndexBestElementDejaAttack := IndexSecondElementToAttack;
       end;

    end;

    // ok, à présent pe est OBLIGATOIREMENT attaqué (car c'est le slot principal)
    NbSondesRestante := FSondesTotales - pe.NbSondesPossibleForCurr;

    // ajoute à la vague
    FListVague.Push(pe.NbSondesPossibleForCurr, pe);

    // incrémente le nombre d'attaque faite, et les valeurs qui vont avec
    IncAtt(pe);

    Result := 1;

    if (NbSondesRestante <= FSondesAQuaiTolere) or (FSlotMaxToUse <= 1) then begin
       // ok, inutile de chercher à compléter un autre slot de flotte
       goto lblFin;
    end;



    // maintenant il faut essayer de trouver une ou plusieurs autres cible
    // pour utiliser le plus de sondes possible en respectant le nombre de slot disponible


    if IndexPremierElementToAttack >= 0 then begin
       // initialisation des indices des éléments à attaquer
       InitCouple(c);
       FSearchBestCouple := c;

       // Définit les bornes de recherche
       FSearchMaxIndice := min(IndexPremierElementToAttack + 10, Count - 1);
       FSearchMinIndice := IndexPremierElementToAttack + 1;

       // Initialise le score
       FSearchBestCoupleLimit := maxint;

       // Finalise la définition des flags
       ReSetTooMuch(NbSondesRestante);

       // Lance la recherche
       StartSearch(c, NbSondesRestante, 2);

       // ajoute les éléments dans la vague
       for i := 0 to MAX_SLOT do begin
          if FSearchBestCouple[i] <> nil then begin
             pe := FSearchBestCouple[i];

             FListVague.Push(pe.NbSondesPossible[pe.NbAttaqueDejaFaite], pe);
             Inc(pe.NbAttaqueDejaFaite);
             if pe.NbAttaqueDejaFaite = 3 then begin
                pe.DateDeDerniereAttaque := DateTimeToUnix(Now);

             end else if pe.NbAttaqueDejaFaite = 1 then begin
                pe.DateDePremiereAttaque := DateTimeToUnix(Now);
                pe.IdVagueDePremiereAttaque := FNbVagueEffectue;
             end;

             Inc(Result);
             
          end;

       end;

    end;

lblFin:


end;

function CompareElements(Item1, Item2: Pointer): Integer;
var e1, e2: PAttSondeCible;
    Dist1, Dist2: integer;
begin


    // Compare renvoie < 0 si Item1 est inférieur à Item2,
    //                   0 s'ils sont égaux
    //                 > 0 si Item1 est supérieur à Item2
    // Voir http://alphomega.developpez.com/TriTList/
    // pour savoir comment trier sur plusieurs critére

    e1 := Item1;
    e2 := Item2;

    Dist1 := e1.Coord.SystemSolaire - TrieCritereProximite;
    if (Dist1 < 0) then
       Dist1 := -1 * Dist1;

    Dist2 := e2.Coord.SystemSolaire - TrieCritereProximite;
    if (Dist2 < 0) then
       Dist2 := -1 * Dist2;


    // Ici on ne compare que deux entiers, donc:
    if Dist1 < Dist2 then
       // Item1 est inférieur à Item2
       Result := -1

    else if Dist1 = Dist2 then
       // Egalité
       Result := 0

    else
       // Sinon Item1 est supérieur à Item2
       Result := 1;

end;


procedure TCollectionAttSondeCible.Trie;
begin
    TrieCritereProximite := FPlaneteSystemSolaireAtt;
    Sort(CompareElements);
end;



{$IFDEF DEBUG}
   procedure TCollectionAttSondeCible.SetDebugProc(Valeur: TsfcProcStatus);
   begin
       FDebugProc := Valeur;
       FListVague.DebugProc := Valeur;
   end;

   procedure TCollectionAttSondeCible.DoDebug(const Msg: string);
   begin
       if Assigned(FDebugProc) then
          FDebugProc(Msg);

   end;
{$ENDIF}





procedure TCollectionAttSondeCible.RefreshnbSondes(pe: PAttSondeCible);
var i64Duree: int64;
    RessourcePriseParAttaque: integer;
    RessourceCapturable: integer;
    TotalRessource: integer;
    RessourcePrise: integer;
    i, k: integer;
begin

(************************************
 Il est clair que l'on envoit TOUJOURS le nombre de sonde necessaire pour prendre
 la moitié des ressources.
 On base les calculs là dessus.
*)

     // pour le retour la place du deut pour le trajet n'existe plus
     RessourcePriseParAttaque := FSondesTotales * 5;

     // PRINCIPE:
     // On part de l'hypothèse que le maximum de sonde a été envoyé pour prendre
     // la moitié des ressources (ou moins de la moitié si on a pas assez de sonde)
     // Le seuil maximal de ressource prise est donc définit par FSondesTotales


     // définit la durée écoulée de la validité pour estimer les ressources
     // qui ont pu être produite depuis le dernier sondage
     i64Duree := FCurrDate - pe.RessourcesValidide;

     TotalRessource := pe.RessourcesInitiales;


     for i := 0 to 2 do begin
         RessourceCapturable := TotalRessource div 2;


         // le raid s'effectue
         if RessourceCapturable >= RessourcePriseParAttaque then begin
            // on envoit toutes les sondes
            pe.NbSondesPossible[i] := FSondesTotales;

            RessourcePrise := RessourcePriseParAttaque;
         end else begin
            // on en envoit moins
            k := RessourceCapturable div 5;
            Inc(k);

            // il se peut que l'on ai trop majoré et que l'on ai dépassé MaxSondeDispo
            pe.NbSondesPossible[i] := min(k, FSondesTotales);

            RessourcePrise := RessourceCapturable;
         end;

         // les ressources qui restent
         Dec(TotalRessource, RessourcePrise);
     end;



end;


procedure TCollectionAttSondeCible.AfficheDebug;
var i: integer;
    pe : PAttSondeCible;
begin

    if Count = 0 then begin
       DoDebug('Aucun élément');
       exit;
    end;

    for i := 0 to Count - 1 do begin
        pe := Items[i];

        DoDebug(format('%s  - %d - %d - %d', [mbtPlanetLocationToStr(pe.Coord), pe.NbSondesPossible[0], pe.NbSondesPossible[1], pe.NbSondesPossible[2]]));
    end;

end;

procedure TCollectionAttSondeCible.RefreshNbSondeToSend;
var i: integer;
    pe : PAttSondeCible;
begin

    if Count = 0 then exit;

    FCurrDate := DateTimeToUnix(Now);

    for i := 0 to Count - 1 do begin
        pe := Items[i];

        if pe.DateDeDerniereAttaque > 100 then
           // déjà attaqué
           Continue;


        RefreshNbSondes(pe);
    end;

end;


function TCollectionAttSondeCible.BuildStats(DB: TCollectionRapportHead): integer;
var i: integer;
    pe : PAttSondeCible;
    peRapport: PRapportHead;
begin

    Result := 0;
    if Count    = 0 then exit;
    if DB.Count = 0 then exit;


    for i := 0 to Count - 1 do begin
        pe := Items[i];

        peRapport := DB.Get_Pointeur(pe.Coord);

        if peRapport <> nil then begin
           pe.Rapport := peRapport;

           pe.RessourcesInitiales := peRapport.Ressource.Metal + peRapport.Ressource.Cristal + peRapport.Ressource.Deuterium;
           pe.RessourcesValidide  := mrGetMostRecentDate(peRapport.DateLastEspionnage);;
        end;

    end;

end;

function TCollectionAttSondeCible.AddIfNotExistCoord(Element: TPlanetLocation): PAttSondeCible;
var i : integer;
    e : TAttSondeCible;
begin

     i := Get_Index(Element);

     if i < 0 then begin
        // L'élément n'existe pas
        // on l'ajoute

        with e do begin
           Coord := Element;

           // Nombre d'attaque déjà effectué
           NbAttaqueDejaFaite := 0;

           // Négatif tant que la dernière attaque n'a pas été effectué
           DateDeDerniereAttaque := -1;
           DateDePremiereAttaque := -1;
           IdVagueDePremiereAttaque := -1;

           // Nombre d'erreur concernant cette planète
           NbErreur := 0;

           // Pointeur vers le rapport
           Rapport   := nil;

           // Nombre de ressource restante
           RessourcesInitiales  := -1;
           RessourcesDejaPrise  := -1;

           // Validité des ressources
           RessourcesValidide  := -1;

           // Définit si il a été pushé en tête de liste
           Pushed   := -1;

           for i := INDICE_FIRST_ATTACK to INDICE_LAST_ATTACK do
               NbSondesPossible[i] := -1;

        end;

        Result := Add(e);


     end else begin
        // L'élément existe déjà
        // On ne le modifie pas
        // renvoi nil pour indiquer que l'élément existe déjà
        Result := nil;
     end;

end;

function TCollectionAttSondeCible.Import(List: TCollectionPlaneteASonder): integer;
var pe: PPlaneteASonder;
    i: integer;
begin

    Result := 0;
    FNbVagueEffectue := 0;

    FCible1 := nil;
    FCible2 := nil;

    if List.Count = 0 then exit;

    for i := 0 to List.Count - 1 do begin
        pe := List.Items[i];
        
        if AddIfNotExistCoord(pe.Coord) <> nil then
           Inc(Result);
    end;

end;


constructor TCollectionAttSondeCible.Create;
begin

    // Héritage
    InHerited Create;
    
    
    
    // Code pour l'initialisation ici
    FNbPushedRestant := 0;
    FDebugProc       := nil;


       // Nombre de sondes totales, c'est à dire le nombre de sondes
       // qui sont envoyées lors d'une première attaque sur un joueur plein de ressource
       FSondesTotales := 6000;

       // Correspond au nombre de slot maximum dédié aux sondes
       // CEPENDANT, si on peut envoyer toutes les sondes sur une cible
       // on on occupera qu'un seul slot
       FSlotMaxToUse  := 2;
       
       FNbVagueEffectue := 0;

       FListVague := TCollAttSlot.Create;

       FSondesAQuaiTolere := 800;

end;

destructor TCollectionAttSondeCible.Destroy;
begin
    // Implémenter le code avant l'héritage de la méthode
    // C'est à dire ici
    
   
    // On appelle NOTRE clear qui lui-même appelle NOTRE Delete qui libère l'espace mémoire
    // avant d'enlever le pointeur de la TList
    // L'héritage de Destroy appelle Clear, mais es-ce le notre ou celui de la classe de base ?
    Clear;

    FListVague.Free;
    
    // Héritage
    InHerited Destroy;
end;


procedure TCollectionAttSondeCible.Clear;
var i: integer;
begin

    if Count > 0 then begin
        For i := Count -1 downto 0 do
            Delete(i);
    end;
    
    inherited Clear;
end;


function TCollectionAttSondeCible.Get_Pointeur(Element: TPlanetLocation): PAttSondeCible;
var i: integer;
begin

   Result := nil;
   
   i := Get_Index(Element);
   if i < 0 then exit;
   Result := Items[i];

end;


function TCollectionAttSondeCible.Get_Index(Element: TPlanetLocation): integer;
var i: integer;
    pElement: PAttSondeCible;
begin

   Result := -1;

   if Count <= 0 then exit;

   for i := 0 to Count - 1 do begin
      pElement := Items[i];

      {### ### ### ### ### ### ### ### ### ### ### ### ### ### }
      {### Neccessite adpation en fonction du type record  ### }
      {### ### ### ### ### ### ### ### ### ### ### ### ### ### }

      if CompareLocation(pElement^.Coord, Element) then begin
         Result := i;
         exit;
      end;
   end;

end;



function TCollectionAttSondeCible.Add_Update(Element: TAttSondeCible): PAttSondeCible;
var i : integer;
begin
     
     Result := nil;
                                         {### ### ### ### ### ### ### ### ### ### ### ### ### ### }
     i := Get_Index(Element.Coord);        {### Neccessite adpation en fonction du type record  ### }
                                         {### ### ### ### ### ### ### ### ### ### ### ### ### ### }
     if i < 0 then begin 
        // L'élément n'existe pas, l'ajoute
        Result  := Add(Element);
     end else begin
        // L'élément existe déjà, le met à jour
        Result  := Items[i];     // Obtient le pointeur
        Result^ := Element;
     end;
     
end;




function TCollectionAttSondeCible.Get_Item(Index: integer): TAttSondeCible;
var pElement: PAttSondeCible;
begin

     if Index < 0 then exit;
     if Index >= Count then exit;

     pElement := Items[Index];     // Obtient le pointeur
     Result := pElement^;

end;

function TCollectionAttSondeCible.Delete(Index: integer): boolean;
var pElement: PAttSondeCible;
begin

    Result := false;

    if (Index < 0) or (Index >= Count) then
       // Hors limite
       exit;


    // Obtient le pointeur
    pElement := Items[Index];

    if pElement <> nil then
       // Libére la mémoire créée lors de l'ajout
       Dispose(pElement);

    // Supprime le pointeur de la liste
    inherited Delete(Index);

    // Met le resultat à True
    Result := true;

end;



function TCollectionAttSondeCible.Add(Element: TAttSondeCible): PAttSondeCible;
var pElement: PAttSondeCible;
begin

    Result := nil;
    
    New(pElement);          // Créer l'espace mémoire requis
    pElement^ := Element;   // Recopie les données dans l'emplacement pointé
    
    try
       inherited Add(pElement);  // Ajoute le pointeur à la liste
       Result := pElement;
    except
       Result := nil;
    end;
    
    // Pour libérer la mémoire occupé par l'élément pointer:  Dispose(pElement);

end;

// Simple ajout de pointeur
procedure TCollectionAttSondeCible.SimpleAdd(Pointeur: PAttSondeCible);
begin
    
    inherited Add(Pointeur);  // Ajoute le pointeur à la liste
    
end;

function TCollectionAttSondeCible.GetItemx(Index: integer): TAttSondeCible;
var pElement: PAttSondeCible;
begin

   if Count < 0 then exit;
   if Index >= Count then exit;

   pElement := Items[Index];
   Result := pElement^;

end;




end.
